package ualrcdg.server;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

import ualrcdg.server.validators.AbstractRuleValidator;
import ualrcdg.shared.exceptions.CDGException;
import ualrcdg.shared.requests.ValidateRuleRequest;

/**
 * Responsible for checking that the conditions of a particular Rule have been met via a number of rule checking implementations.
 * These implementations accept a set of "target" parameters along with data gathered from a Player and their mobile device and will process that data and validate that the "target" condition has been met.
 */
public class RuleValidator
{
	private final String validatorListing;
	private final HashMap< String, AbstractRuleValidator > validators;
	private final ClassLoader loader = ClassLoader.getSystemClassLoader( );
	
	/**
	 * Primary constructor
	 *  
	 * @param rootPath the path to the folder (relative to the current working directory) that contains all files and folders necessary for the CDG Server application
	 * @param validatorListing the name of the file (within the rootPath) that describes the currently-implemented rule validators
	 * @throws IOException If an IO error occurs while registering all recognized AbstractRuleValidators
	 * @throws CDGException If a recognized AbstractRuleValidator cannot be registered 
	 */
	public RuleValidator( String rootPath, String validatorListing ) throws IOException, CDGException
	{
		System.out.println( "RuleValidator(" + rootPath + ", " + validatorListing + ")" );
		this.validatorListing = rootPath + validatorListing;

		validators = new HashMap< String, AbstractRuleValidator >( );
		if(validatorListing != null )
		{
			BufferedReader ruleList = new BufferedReader( new FileReader( new File( this.validatorListing ) ) );
			String ruleType = null;
			String ruleClassName = null;
			while( ( ruleType = ruleList.readLine( ) ) != null  && ( ruleClassName = ruleList.readLine( ) ) != null )
			{
				registerValidator( ruleType, ruleClassName );
			}
			
			ruleList.close( );
		}
	}
	
	/**
	 * Writes all registered AbstractRuleValidators to the validatorListing file established in the constructor
	 * 
	 * @throws FileNotFoundException If the validatorListing file cannot be located
	 * @throws IOException If an IO error occurs while writing the new validatorListing file
	 */
	public void commit( ) throws FileNotFoundException
	{
		PrintWriter out = new PrintWriter( new File( validatorListing ) );

		for( Map.Entry< String, AbstractRuleValidator > v : validators.entrySet( ) )
		{
			out.println( v.getKey( ) );
			out.println( v.getValue( ).getClass( ).getName( ) );
			System.out.println( "RuleValidator: committed " + v.getKey( ) + " " + v.getValue( ).getClass( ).getName( ) );
		}
		
		out.close( );
	}
	
	/**
	 * Attempts to register an AbstractRuleValidator class with this RuleValidator instance
	 * 
	 * @param ruleType the name of the rule type with which the specified AbstractRuleValidator is to be associated 
	 * @param validatorClassName the name of the class that implements the validation process of the specified rule type
	 * @throws CDGException If an AbstractRuleValidator is already registered for the given rule type
	 * @throws ReflectiveOperationException If the rule validation implementation class cannot be located or loaded.
	 */
	//LATER Consider adding parameter allowing RuleValidator instances to be replaced
	public void registerValidator( String ruleType, String validatorClassName ) throws CDGException
	{
		if( validators.containsKey( ruleType ) )
		{
			throw new CDGException( "Unable to register validator with duplicate ruleType [" + ruleType + "]." );
		}
		
		try
		{
			Class< ? > validatorClass = loader.loadClass( validatorClassName );
			Constructor<?> nullaryContructor = validatorClass.getDeclaredConstructor( );
			nullaryContructor.setAccessible( true );
			AbstractRuleValidator o = ( AbstractRuleValidator ) nullaryContructor.newInstance( );
			
			validators.put( ruleType, o.getInstance( ) );
			
			System.out.println( "RuleValidator: created mapping " + ruleType + " -> new " + validatorClass.getName( ) );
		}
		catch( NoSuchMethodException | InvocationTargetException | InstantiationException e )
		{
			e.printStackTrace( );
			throw new CDGException( "RuleValidator: Unable to resgister class[" + validatorClassName + "] to validate rules of type[" + ruleType + "]", e );
		}
		catch( ClassNotFoundException | IllegalAccessException e )
		{
			e.printStackTrace();
			throw new CDGException( "RuleValidator: Unable to resgister class[" + validatorClassName + "] to validate rules of type[" + ruleType + "]", e );
		}
	}
	
	/**
	 * Attempts to determine whether or not a given rule has been completed or achieved
	 * 
	 * @param request a ValidateRuleRequest object containing the parameters of the rule validation request
	 * @return true if the rule has been determined to be completed, false otherwise
	 */
	public boolean validateRule( ValidateRuleRequest request ) throws IOException
	{
		AbstractRuleValidator validator = validators.get( request.type );
		if( validator == null )
		{
			System.out.println( "RuleValidator: unable to validate rule of type [" + request.type + "]." );
			return false;
		}
		
		ByteArrayInputStream bais = new ByteArrayInputStream( request.data.getBytes( ) );
		
		boolean result = validator.validateRule( request.type, request.parameters, bais ); 
		
		bais.close( );
		
		return result; 
	}
}
